/**
 * ChessBoard.java
 * This file contains most of the game logic.
 * @author Tyler Eastman
 */
package bbchess.units;

import FICS.GameOver;
import bbchess.GameManager;
import bbchess.dialogs.HistoryDialog;
import bbchess.MainGui;
import bbchess.units.pieces.*;
import bbchess.util.ImageLoader;
import bbchess.util.SoundLoader;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JPanel;
import java.awt.MouseInfo;
import java.awt.Point;

/**
 *
 * @author Tyler Eastman
 */
public class ChessBoard extends JPanel {
    // declare variables
    BufferedImage board = null;
    BufferStrategy bufferStrategy;
    private ChessSquare[][] chessSquaresArray;
    private Map<String, ChessSquare> chessSquares;
    private ArrayList<ChessPiece> chessPieces;
    private ArrayList<ChessPiece> capturedPieces;
    ArrayList<ChessSquare> threatenedSquares;
    ChessMouseListener chessMouseListener;
    private HistoryDialog historyDialog;
    public MainGui gui;
    private GameManager gameManager;
    private String uciHistory;
    private String isCheck;
    private String boardOrientation;
    private King whiteKing;
    private King blackKing;
    private Rook whiteKingSideRook;
    private Rook whiteQueenSideRook;
    private Rook blackKingSideRook;
    private Rook blackQueenSideRook;
    private Thread chessSquareUpdateThread;

    public ChessBoard(MainGui gui) {

        // initialize variables
        chessSquares = new HashMap<String, ChessSquare>();
        chessSquaresArray = new ChessSquare[8][8];
        chessPieces = new ArrayList<ChessPiece>();
        capturedPieces = new ArrayList<ChessPiece>();
        threatenedSquares = new ArrayList<ChessSquare>();
        chessMouseListener = new ChessMouseListener();
        historyDialog = null;
        this.gui = gui;
        gameManager = null;
        uciHistory = "";
        isCheck = "";
        boardOrientation = "white";

        addMouseListener(chessMouseListener);
        addMouseMotionListener(chessMouseListener);

        setLayout(new java.awt.GridLayout(0, 8));

        initSquares();
        initPieces();

        // start game loop
        chessSquareUpdateThread = new Thread(new ChessSquareUpdateThread());
        chessSquareUpdateThread.start();
    }

    /**
     * Resets the board, squares, and pieces to their original state.  Called by
     * GameManager::resetGame()
     */
    public void resetBoard() {

        // remove all pieces from local variables
        chessPieces.clear();
        capturedPieces.clear();

        // reset board squares to original state
        resetSquares();

        // reset mouse variables
        chessMouseListener.resetMouseVariables();

        // reset check
        isCheck = "";

        // clear the UCI history
        clearUciHistory();

        // make sure board is facing the right way
        if (getBoardOrientation().equals("black")) {
            changeBoardOrientation();
        }

        // initialize pieces
        initPieces();

        getParent().repaint();
    }

    //@Override
    public void paintComponent(Graphics g) {
        g.drawImage(ImageLoader.getBoardImage(), 0, 0, getWidth(), getHeight(), this);
    }

    /**
     * returns the size of a game square
     * @return size of a game square
     */
    public int getSquareSize() {
        return getWidth()/8;
    }

    private void initSquares() {
        for (int i = 7; i >= 0; i--) {
            for (int j = 0; j < 8; j++) {
                ChessSquare cs;
                switch (j) {
                    case 7:
                        cs = new ChessSquare("h" + (i + 1));
                        break;
                    case 6:
                        cs = new ChessSquare("g" + (i + 1));
                        break;
                    case 5:
                        cs = new ChessSquare("f" + (i + 1));
                        break;
                    case 4:
                        cs = new ChessSquare("e" + (i + 1));
                        break;
                    case 3:
                        cs = new ChessSquare("d" + (i + 1));
                        break;
                    case 2:
                        cs = new ChessSquare("c" + (i + 1));
                        break;
                    case 1:
                        cs = new ChessSquare("b" + (i + 1));
                        break;
                    case 0:
                        cs = new ChessSquare("a" + (i + 1));
                        break;
                    default:
                        cs = new ChessSquare("??");
                }
                chessSquaresArray[i][j] = cs;
                chessSquares.put(cs.getPosition(), cs);
                add(cs);
            }
        }

        this.validate();
    }

    @Override
    public void paint(Graphics g) {
        paintComponent(g);
        paintComponents(g);
    }

    @Override
    public void update(Graphics g) {
        paint(g);
    }

    private void initPieces() {

        //initialize and add chesspieces
        chessPieces.add(whiteQueenSideRook = new Rook("white", chessSquaresArray[0][0]));
        chessPieces.add(new Knight("white", chessSquaresArray[0][1]));
        chessPieces.add(new Bishop("white", chessSquaresArray[0][2]));
        chessPieces.add(new Queen("white", chessSquaresArray[0][3]));
        chessPieces.add(new Bishop("white", chessSquaresArray[0][5]));
        chessPieces.add(new Knight("white", chessSquaresArray[0][6]));
        chessPieces.add(whiteKingSideRook = new Rook("white", chessSquaresArray[0][7]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][0]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][1]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][2]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][3]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][4]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][5]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][6]));
        chessPieces.add(new Pawn("white", chessSquaresArray[1][7]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][0]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][1]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][2]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][3]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][4]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][5]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][6]));
        chessPieces.add(new Pawn("black", chessSquaresArray[6][7]));
        chessPieces.add(blackQueenSideRook = new Rook("black", chessSquaresArray[7][0]));
        chessPieces.add(new Knight("black", chessSquaresArray[7][1]));
        chessPieces.add(new Bishop("black", chessSquaresArray[7][2]));
        chessPieces.add(new Queen("black", chessSquaresArray[7][3]));
        chessPieces.add(new Bishop("black", chessSquaresArray[7][5]));
        chessPieces.add(new Knight("black", chessSquaresArray[7][6]));
        chessPieces.add(blackKingSideRook = new Rook("black", chessSquaresArray[7][7]));

        /*
         * kings added last so they are the bottom of the chesspieces list.  Therefore
         * they are calculated last in any equation, which is important due to their
         * special movement pattern.  (ie, not being able to move to a threatened square)
         */
        chessPieces.add(whiteKing = new King("white", chessSquaresArray[0][4]));
        chessPieces.add(blackKing = new King("black", chessSquaresArray[7][4]));

        // add newPiece to board
        for (ChessPiece piece : getChessPieces()) {
            piece.setBoard(this);
        }

        // update all chess moves
        updateChessMoves();

        validate();
    }

    public void updateUciHistory(String move) {
        if (uciHistory.length() == 0) {
            uciHistory += move;
        } else {
            uciHistory += " " + move;
        }
    }

    public void clearUciHistory() {
        uciHistory = "";
    }

    public void getMove(String move) {

        updateUciHistory(move);

        String start = move.substring(0, 2);
        String end = move.substring(2, 4);
        String promotion = null;

        

        try {
            promotion = move.substring(4, 5);
        } catch (Exception ex) {/*no promotion, ignore*/

        }

        // find piece at starting location
        ChessSquare startSquare = getChessSquares().get(start);
        ChessSquare endSquare = getChessSquares().get(end);
        ChessPiece piece = startSquare.getContents();

        // is end a legal move for piece?
        if (legalMove(piece, endSquare)) {
            setMove(piece, endSquare);
        }

        // can we promote
        Pawn pawnPiece = null;

        if (piece instanceof Pawn) {
            pawnPiece = (Pawn) piece;

            if (promotion != null) {

                if (promotion.equals("Q")) {
                    pawnPiece.promoteTo("Q");
                    SoundLoader.drop();
                }
                if (promotion.equals("N")) {
                    pawnPiece.promoteTo("N");
                    SoundLoader.drop();
                }
                if (promotion.equals("R")) {
                    pawnPiece.promoteTo("R");
                    SoundLoader.drop();
                }
                if (promotion.equals("B")) {
                    pawnPiece.promoteTo("B");
                    SoundLoader.drop();
                }
            }
        }

        getParent().repaint();
    }

    // returns pointer to chessSquare the mouse is over
    public ChessSquare getMouseLocation() {
        try {

            for (ChessSquare square : chessSquares.values()) // if mouse is within square
            {
                if (square.getMousePosition() != null) {
                    return square;
                }
            }

            // else loop didnt work
            return null;

            // if error
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * @param historyDialog the historyDialog to set
     */
    public void setHistoryDialog(HistoryDialog historyDialog) {
        this.historyDialog = historyDialog;
    }

    /**
     * Clears the board of possible moves
     */
    public void clearMoves() {
        for (ChessPiece piece : getChessPieces()) {
            piece.getPosition().setCurrent(false);
            piece.showLegalMoves(false);
        }
    }

    /**
     * @return the chessPieces
     */
    public ArrayList<ChessPiece> getChessPieces() {
        return chessPieces;
    }

    /*
     * calculates a path based on a given position, number of iterations, and movement paramaters.
     * used in collision detection and pathfinding.  If the isKing boolean is set to true,
     * the path returned is the accurate threat path of the piece in question, taking into
     * account the kings location, and other newPiece of the same color.
     */
    public ArrayList<String> calculatePath(String currentPos, int iterations, int moveX,
            int moveY, boolean ignoreKing) {
        ArrayList<String> path = new ArrayList<String>();
        String newPosition = currentPos;
        boolean occupied = false;

        try {
            // repeat this loop for each iteration
            for (int x = 0; x < iterations; x++) {
                // make sure we havent already collided with a piece
                if (!occupied) {
                    newPosition = calculatePosition(newPosition, moveX, moveY);

                    // make sure theres no piece occupying path
                    for (ChessPiece newPiece : getChessPieces()) {
                        // if theres already a piece occupying the square, set flag occupied
                        if (newPiece.getPosition().getPosition().equals(newPosition)) {

                            if (ignoreKing) {

                                ChessPiece currentPiece = getChessSquares().get(currentPos).getContents();

                                // if the piece is an opposing king, skip it (to make sure it cant move to s square
                                // behing it)
                                if ((newPiece instanceof King)
                                        && !(newPiece.getColor().equals(currentPiece.getColor()))) {
                                    // skip over opposite colors king
                                } // else if the newPiece color is equal to the one we're calculating the path with
                                // add the piece to the path, and set occupied to true.
                                else if (newPiece.getColor().equals(currentPiece.getColor())) {
                                    path.add(newPosition);
                                    occupied = true;
                                } else if (!newPiece.getColor().equals(currentPiece.getColor())) {
                                    occupied = true;
                                }
                            } // else if we're not ignoring the king, go ahead and set occupied
                            else {
                                // otherwise set occupied to true
                                occupied = true;
                            }
                        }
                    }

                    // if square is not occupied add to new position
                    if (!occupied) {

                        // if new position is not null (ie, off board)
                        if (newPosition != null) {
                            path.add(newPosition);
                        }
                    }
                }
            }
        } catch (Exception ex) {/* new position out of bounds, ignore it */

        }
        return path;
    }

    // This is the overriden version of calculateCapture that doesn't take in a piece to skip over.
    public String calculateCapture(String currentPos, String color, int iterations, int moveX, int moveY) {
        return calculateCapture(currentPos, color, iterations, moveX, moveY, null);
    }

    // This method returns the first piece of the opposite color that we can capture,
    // skipping over the pieceToSkip.
    public String calculateCapture(String currentPos, String color, int iterations,
            int moveX, int moveY, ChessPiece pieceToSkip) {
        ChessPiece currentPiece = getChessSquares().get(currentPos).getContents();
        String newPosition = currentPos;
        boolean occupied = false;
        boolean skipped = false;

        try {
            // repeat this loop for each iteration
            for (int x = 0; x < iterations; x++) {
                // make sure we havent already collided with a piece
                if (!occupied) {
                    newPosition = calculatePosition(newPosition, moveX, moveY);

                    // make sure theres no piece occupying path
                    for (ChessPiece pieces : getChessPieces()) {
                        // if theres already a piece occupying the square, set flag occupied
                        if (pieces.getPosition().getPosition().equals(newPosition) && pieces != pieceToSkip) {
                            occupied = true;
                            // if the piece is of opposite color, return piece
                            if (!pieces.getColor().equals(color)) {
                                return pieces.getPosition().getPosition();
                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {/* new position out of bounds, ignore it */

        }
        // all else fails, return null
        return null;
    }

    // Static helper method that determines a new position based on new x and y movements
    public static String calculatePosition(String currentPos, int moveX, int moveY) {
        char[] xArray = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
        char[] yArray = {'1', '2', '3', '4', '5', '6', '7', '8'};

        // cast the current position into a char array for easy manipulation
        char[] posArray = currentPos.toCharArray();

        // for each of the elements of the char array
        for (int countX = 0; countX < 8; countX++) {
            // if we've found what the x coord is
            if (xArray[countX] == posArray[0]) {
                try {
                    // increment or decrement the x coord of posArray, return null if out of bounds
                    posArray[0] = xArray[countX += moveX];
                    break;
                } catch (Exception ex) {
                    return null;
                }
            }
        }

        for (int countY = 0; countY < 8; countY++) {
            // if we've found what the y coord is
            if (yArray[countY] == posArray[1]) {
                try {
                    // increment or decrement the x coord of posArray, return null if out of bounds
                    posArray[1] = yArray[countY += moveY];
                    break;
                } catch (Exception ex) {
                    return null;
                }
            }
        }

        return "" + posArray[0] + posArray[1];
    }

    public void setGameManager(GameManager gameManager) {
        this.gameManager = gameManager;
    }

    /**
     * @return the chessSquares
     */
    public Map<String, ChessSquare> getChessSquares() {
        return chessSquares;
    }

    /**
     * The mouse listener class for the whole chess game.
     */
    public class ChessMouseListener implements MouseListener, MouseMotionListener {

        ChessPiece piece = null;
        ChessSquare square = null;
        ChessSquare highlightedSquare = null;
        ChessSquare mouseOverSquare = null;

        boolean mouseClickedOverPiece = false;
        boolean mouseMoved = false;
        boolean draggingPiece = false;

        /**
         * resets all mouse variables to their default state, this is useful
         * if the game goes out of focus and the player is still dragging a piece.
         */
        public void resetMouseVariables() {
            piece = null;
            square = null;
            highlightedSquare = null;
            mouseOverSquare = null;
            mouseClickedOverPiece = false;
            mouseMoved = false;
            draggingPiece = false;
        }

        public void mousePressed(MouseEvent e) {

            // if input isn't blocked
            if (!gui.glassPane.isScreenDisabled()) {
                // if mousebutton 1 is pressed
                if (e.getButton() == 1) {
                    square = getMouseLocation();

                    for (ChessPiece currentPiece : getChessPieces()) {

                        if (currentPiece.getPosition() == square
                                && gameManager.getTurn().equals(currentPiece.getColor())
                                && gameManager.canMove()) {

                            piece = currentPiece;
                            mouseMoved = false;
                            mouseClickedOverPiece = true;
                        }
                    }
                }
            }
        }

        public void mouseReleased(MouseEvent e) {

            boolean legalMove = false;

            if (e.getButton() == 1) {

                if (piece != null) {

                    square = getMouseLocation();
                    legalMove = legalMove(piece, square);

                    // If mouse is released over same piece
                    if (mouseClickedOverPiece && !mouseMoved) {

                        // left button released over valid piece, mouse hasn't moved
                        clearMoves();

                        // if we haven't clicked the same square, select new square
                        if (highlightedSquare != square && square != null) {

                            square.setCurrent(true);
                            piece.showLegalMoves(true);
                            highlightedSquare = square;
                        } else {
                            highlightedSquare = null;
                        }

                        mouseClickedOverPiece = false;
                        mouseMoved = false;
                    } else if (draggingPiece && !legalMove) {

                        // left button released, releasing dragged piece over non-legal square
                        clearMoves();
                        gui.glassPane.moveToSquare(piece, piece.getPosition());
                        gui.glassPane.stopDragging();
                        piece = null;
                        draggingPiece = false;
                        mouseClickedOverPiece = false;
                        mouseMoved = false;
                        piece = null;
                    } else if (draggingPiece && legalMove) {

                        // left button released, releasing dragged piece over legal square
                        setMove(piece, square);
                        clearMoves();
                        piece = null;
                        gui.glassPane.stopDragging();
                        piece = null;
                        draggingPiece = false;
                        mouseClickedOverPiece = false;
                        mouseMoved = false;
                    } else if (legalMove) {

                        // left button released over legal square, no dragged piece
                        setMove(piece, square);
                        clearMoves();
                        piece = null;
                        draggingPiece = false;
                        mouseClickedOverPiece = false;
                        mouseMoved = false;
                    } else {
                        // left button released over empty square, no dragged piece

                        clearMoves();
                        piece = null;
                        draggingPiece = false;
                        mouseClickedOverPiece = false;
                        mouseMoved = false;
                    }
                }

                repaint();
            }
        }

        public void mouseEntered(MouseEvent e) {
            //
        }

        public void mouseExited(MouseEvent e) {
            //
        }

        public void mouseClicked(MouseEvent e) {
            //
        }

        public void mouseDragged(MouseEvent e) {
            mouseMoved = true;
            Point mouseLoc = new Point();
            mouseLoc.setLocation(MouseInfo.getPointerInfo().getLocation().x,
                    MouseInfo.getPointerInfo().getLocation().y);

            javax.swing.SwingUtilities.convertPointFromScreen(mouseLoc, getParent().getParent());

            if (mouseClickedOverPiece && (piece != null)) {
                clearMoves();
                square.setCurrent(true);
                piece.showLegalMoves(true);
                highlightedSquare = square;

                // if first time called
                if (!draggingPiece) {
                    gui.glassPane.startDragging(piece);
                    draggingPiece = true;
                }
            }
        }

        public void mouseMoved(MouseEvent e) {
            //
        }
    }

    public void changeTurn() {
        // Change turn
        try {
            if (gameManager.getTurn().equals("white")) {

                gameManager.setTurn("black");
                gui.updateTurnStatus("black");

                if (gameManager.canMove() && !getBoardOrientation().equals("black")) {
                    changeBoardOrientation();
                }

            } else {

                gameManager.setTurn("white");
                gui.updateTurnStatus("white");

                if (gameManager.canMove() && !getBoardOrientation().equals("white")) {
                    changeBoardOrientation();
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Returns the current board orientation
     * @return
     */
    private String getBoardOrientation() {
        return boardOrientation;
    }

    /**
     * Changes the current board orientation from white to black, or black to white
     */
    private void updateBoardOrientation() {
        if (boardOrientation.equals("white")) {

            boardOrientation = "black";
        } else {
            boardOrientation = "white";
        }
    }

    /*
     * Attempts to move piece to square, returns a boolean based on success
     */
    public boolean setMove(ChessPiece piece, ChessSquare square, boolean updateTurn) {
        // clear last moves, and set contents of current square to null;
        clearLastMoveSquares();
        piece.getPosition().clearContents();

        String oldLocation = piece.getPosition().getPosition();

        // Set move
        piece.addLastMoves(piece.getPosition().getPosition());
        piece.setPosition(square);
        piece.addLastMoves(square.getPosition());
        piece.setMove(piece.getMove() + 1);

        // Update
        updateHistory(piece, square);

        piece.setLastMoves(true);
        piece.clearLastMoves();
        clearSquares();
        updateChessMoves();
        adjustChessMoves();

        // check for mate
        String check = isCheck();

        // clear newPiece threatenedBy list
        for (ChessPiece tempPiece : chessPieces) {
            tempPiece.clearThreatenedBy();
        }

        // move piece
        //ChessPiece.moveToSquare(piece, piece.getPosition());
        castleMove(square, piece);

        String promotedUnit = "";

        if (gameManager.canMove()) {
            // if we promoted, getChessPiece().get(0) will be different after tryToPromote
            // has been called
            ChessPiece oldPiece = getChessPieces().get(0);

            piece.tryToPromote();

            ChessPiece newPiece = getChessPieces().get(0);

            if (oldPiece != newPiece) {
                promotedUnit = newPiece.getUnit();
            }
        }

        if (!updateTurn) {

            // Send move to engine if we're the player and playing the computer
            if (gameManager.canMove() && (gameManager.getComputer() != null)) {
                sendMoveToEngine(oldLocation, square.getPosition() + promotedUnit);
            }

            // Send move to network if were playing online
            if (gameManager.canMove() && (gameManager.isPlayingOnline())) {
                sendMoveToNetwork(oldLocation, square.getPosition() + "=" + promotedUnit);
            }

            if (check.contains("checkmate:")
                    || check.contains("draw:")) {
                endGame(check);
            }

            // Change turn
            changeTurn();
        }

        return true;
    }

    public boolean setMove(ChessPiece piece, ChessSquare square) {
        return setMove(piece, square, false);
    }

    /**
     * Called within ChessPiece::moveToSquare() to determine if the move made was a castle move.
     * If so, this method sets
     * @param square
     * @param piece
     */
    public boolean castleMove(ChessSquare square, ChessPiece piece) {
        // did we just move a castlable king to a castle square?
        if (piece instanceof King) {
            String castle = "";
            // if piece is the white king
            if (piece == whiteKing) {
                castle = whiteKing.getKingCanCastle();
                whiteKing.setKingCanCastle("");

                if (!castle.equals("")) {
                    if (square.getPosition().equals("c1")) {
                        setMove(whiteQueenSideRook, getChessSquares().get("d1"), true);
                    } else if (square.getPosition().equals("g1")) {
                        setMove(whiteKingSideRook, getChessSquares().get("f1"), true);
                    }

                    return true;
                }
            } // if piece is the black king
            else {
                castle = blackKing.getKingCanCastle();
                blackKing.setKingCanCastle("");

                if (!castle.equals("")) {
                    if (square.getPosition().equals("c8")) {
                        setMove(blackQueenSideRook, getChessSquares().get("d8"), true);
                    } else if (square.getPosition().equals("g8")) {
                        setMove(blackKingSideRook, getChessSquares().get("f8"), true);
                    }

                    return true;
                }
            }
        }

        // else the piece is not castleable
        return false;

    }

    /*
     * Updates the chesspieces movement/capture/and threatened moves information
     * and clears the board.
     */
    public void clearSquares() {
        clearCurrentSquare();
        clearLegalSquares();
        clearCapturableSquares();
        clearThreatenedSquares();
    }

    /* Clears the last move squares, used for display purposes
     * and called by setMove()
     */
    public void clearLastMoveSquares() {
        for (ChessSquare square : chessSquares.values()) {
            square.setLastMove(false);
        }
    }

    /*
     * Determines if a move by piece to square is legal, returns a boolean
     * based on success.
     */
    private boolean legalMove(ChessPiece piece, ChessSquare square) {

        if (square == null) {
            return false;
        }
        
        String squareString = square.getPosition();

        // if the square is a legal piece move, return true
        for (String move : piece.getLegalMoves()) {
            if ((move != null) && (move.equals(squareString))) {
                return true;
            }
        }

        // if the square is a capturable piece move, return true
        for (String move : piece.getCapturableMoves()) {
            if ((move != null) && (move.equals(squareString))) {
                // set the piece currently occupying the square to captured
                ChessSquare capturableSquare = getChessSquares().get(squareString);
                ChessPiece capturablePiece = capturableSquare.getContents();

                // if theres a piece there
                if (capturablePiece != null) {
                    capturablePiece.capture(true);
                    return true;
                } // else its an enpassant capture, find the appropriate piece and
                // capture it
                else {
                    for (ChessPiece passantPiece : getChessPieces()) {
                        if (passantPiece instanceof Pawn) {
                            Pawn passantPawn = (Pawn) passantPiece;

                            // capture the enPassant piece
                            if (passantPawn.getCapturableEnPassant()) {
                                passantPawn.capture(true);
                                return true;
                            }
                        }
                    }
                }
            }
        }

        // all else fails, return false
        return false;
    }

    /*
     * Updates every newPiece movement abilities
     */
    public void updateChessMoves() {
        updateEnPassantPawns();
        for (ChessPiece piece : getChessPieces()) {
            piece.updateMoves();
        }
    }

    /**
     * Cycles through all of the pawns on the board and determines if they are capturable
     * by an enpassant move.  This needs to be called before we update the moves so that
     * the pawns can update their capture squares properly.
     */
    private void updateEnPassantPawns() {
        for (ChessPiece piece : getChessPieces()) {
            // first, if the piece is a pawn, we must calculate if it's capturable via enpassant
            if (piece instanceof Pawn) {
                Pawn tempPawn = (Pawn) piece;
                tempPawn.calculateCapturableEnPassant();
            }
        }
    }

    /**
     * Adjusts every newPiece movement abilities.
     * to be called after every piece has updated.
     */
    public void adjustChessMoves() {
        for (ChessPiece piece : getChessPieces()) {
            piece.adjustMoves();
        }
    }

    /**
     * determines if the situation is a draw/check/or checkmate
     * returns a string with the result
     *
     * @return Check, Checkmate, or Draw
     */
    public String isCheck() {
        // initialize variables
        boolean check = false;
        boolean checkmate = false;
        int kingcount = 0;
        String pieceColor = null;
        String piecePosition = null;

        while (kingcount != 2) {
            // loop through newPiece to find each king
            for (ChessPiece piece : getChessPieces()) {
                // if the piece is a king
                if (piece instanceof King) {
                    // reset threatened moves
                    kingcount++;

                    // check kings color, and gather opposite colors threat squares
                    if (piece.getColor().equals("white")) {
                        clearThreatenedSquares();
                        updateBlackThreatenedMoves();
                    } else {
                        clearThreatenedSquares();
                        updateWhiteThreatenedMoves();
                    }


                    // if kings location is in a threatened square, set check to true
                    if (piece.isThreatened()) {
                        // set check to true
                        check = true;
                        // save piece position and color for display purposes
                        pieceColor = piece.getColor();
                        piecePosition = piece.getPosition().getPosition();
                    }

                    // if the king is in threat, and cant move, we need to check if there
                    // is any other way for the king to get out of this predicament, IE
                    // capturing the pinning piece, or intercepting the pin with
                    // another piece.
                    if (piece.isThreatened() && (piece.getLegalMoves().isEmpty()) && (piece.getCapturableMoves().isEmpty())) {
                        // save piece location and color for display purposes
                        pieceColor = piece.getColor();
                        piecePosition = piece.getPosition().getPosition();

                        // assume checkmate is true
                        checkmate = true;

                        // if we can capture the piece thats pinning us, that piece would have
                        // another piece in it's threatenedBy ArrayList, lets check it
                        if ((piece.getThreatenedBy().size() == 1)
                                && (piece.getThreatenedBy().get(0).isThreatened())) {
                            if ((!piece.getThreatenedBy().get(0).getThreatenedBy().contains(piece))) {
                                // we're not in checkmate!  the piece thats threatening the king can
                                // be killed by one of our newPiece!
                                gui.getStatusPanel().setCheckLabelText("Check!");
                                checkmate = false;
                                check = true;
                            }
                        }

                        // if we can intercept the piece thats pinning us, it has to be either
                        // a rook, bishop, or queen, and the king can only be under threat by
                        // one of them! If these conditions are met, then lets look at the pinning
                        // piece's movement squares and check them against our remaining newPiece (except the king's)
                        // movement squares, thereby telling us if we can intercept in the next move.
                        if ((piece.getThreatenedBy().size() == 1)
                                && ((piece.getThreatenedBy().get(0) instanceof Rook)
                                || (piece.getThreatenedBy().get(0) instanceof Bishop)
                                || (piece.getThreatenedBy().get(0) instanceof Queen))) {

                            // can it be intercepted?
                            for (String capturePathSquare : piece.getThreatenedBy().get(0).getKingCapturePath()) {
                                for (ChessPiece remainingPieces : chessPieces) {
                                    if (!(remainingPieces instanceof King)
                                            && (remainingPieces.getColor().equals(piece.getColor()))) {
                                        for (String remainingPiecesLegalMove : remainingPieces.getLegalMoves()) {
                                            if (remainingPiecesLegalMove.equals(capturePathSquare)) {
                                                gui.getStatusPanel().setCheckLabelText("Check!");
                                                check = true;
                                                checkmate = false;
                                            }
                                        }
                                    }
                                }
                            }

                            // can it be killed?
                            for (ChessPiece remainingPiece : chessPieces) {
                                if (remainingPiece.getColor().equals(piece.getColor())) {
                                    for (String remainingPiecesCaptureMove : remainingPiece.getCapturableMoves()) {
                                        if (remainingPiecesCaptureMove.equals(piece.getThreatenedBy().get(0).getPosition().getPosition())) {
                                            gui.getStatusPanel().setCheckLabelText("Check!");
                                            check = true;
                                            checkmate = false;
                                        }
                                    }
                                }
                            }

                        }
                        if (check && checkmate) {
                            // if king is in check, return checkmate
                            gui.getStatusPanel().setCheckLabelText("Checkmate!");
                            isCheck = "checkmate";
                            return "checkmate: " + pieceColor + " (" + piecePosition + ")";

                        } else if (!check) {
                            // else return draw
                            gui.getStatusPanel().setCheckLabelText("Draw!");
                            isCheck = "draw";
                            return "draw: " + pieceColor + " (" + piecePosition + ")";
                        }
                    }
                }


                // else if check is true, return "check"
                if (check) {
                    gui.getStatusPanel().setCheckLabelText("Check!");
                    isCheck = "check";
                    return "check: " + pieceColor + " (" + piecePosition + ")";
                }
            }
        }

        // else, return "no check/checkmate found"
        gui.getStatusPanel().setCheckLabelText("");
        isCheck = "";
        return "no check/checkmate found";
    }

    /**
     * Once a checkmate has been found, this method is called
     */
    public void endGame(String inText) {
        // if we're playing online, show the online version of the gameover dialog
        if (gameManager.isPlayingOnline()) {
            new GameOver(gameManager.getFicsInterface(), true, gui.getTelnetInterface(null, null), inText).setVisible(true);
        }
    }

    /**
     * Updates all white pieces threatened moves.
     */
    public void updateWhiteThreatenedMoves() {
        for (ChessPiece piece : getChessPieces()) {
            if (piece.getColor().equals("white")) {
                piece.setThreatenedMoves(true);
            }
        }
    }

    /**
     * Updates all black pieces threatened moves.
     */
    public void updateBlackThreatenedMoves() {
        for (ChessPiece piece : getChessPieces()) {
            if (piece.getColor().equals("black")) {
                piece.setThreatenedMoves(true);
            }
        }
    }

    /**
     * Updates the history by incrementing the move number, and appending the
     * move to the history dialog
     * @param piece: the piece that moved
     * @param square: the square it moved to
     */
    private void updateHistory(ChessPiece piece, ChessSquare square) {
        String newMove = "";
        if (historyDialog != null) {
            // if turn is black, add new moveNumber to history
            if (gameManager.getTurn().equals("black")) {
                Integer move = gameManager.getMoveNumber();
                newMove += move.toString() + ". ";
                gameManager.incrementMoveNumber();
            }

            newMove += piece.getUnit();
            newMove += square.getPosition() + " ";
            historyDialog.appendText(newMove);
        }
    }

    /**
     * Sends the move start->end to the game engine
     * @param start: starting position of the move
     * @param end: ending position of the move
     */
    private void sendMoveToEngine(String start, String end) {
        updateUciHistory(start + end);
        gui.engineInterface.tellEngine("position moves " + uciHistory);
        gui.engineInterface.waitForNextMove(gameManager.getDepth());
        gui.engineInterface.setIsReady(false);
    }

    /**
     * Sends the move start->end to the telnet interface
     * @param start: starting position of the move
     * @param end: ending position of the move
     */
    private void sendMoveToNetwork(String start, String end) {
        try {
            start = start.trim();
            end = end.trim();
            gui.getTelnetInterface(null, null).tellTelnet(start + end);
        } catch (Exception ex) {
        }
    }

    /**
     * Flips the boards orientation from b->w and w->b
     */
    public void changeBoardOrientation() {

        // if orientation is white, flip one direction
        if (boardOrientation.equals("white")) {
            for (int i = 0 ; i < 8; i++) {
                for (int j = 7; j >= 0; j--) {
                    ChessSquare cs = chessSquaresArray[i][j];
                    remove(cs);
                    add(cs);
                }
            }
        }

        // else orientation must be black, flip back
        else {
            for (int i = 7; i >= 0; i--) {
                for (int j = 0 ; j < 8; j++) {
                    ChessSquare cs = chessSquaresArray[i][j];
                    remove(cs);
                    add(cs);
                }
            }
        }

        // to prevent screen redraw issues, repaint the board.
        repaint();
        updateBoardOrientation();
    }

    public void clearCurrentSquare() {
        for (ChessSquare square : chessSquares.values()) {
            square.setCurrent(false);
        }
    }

    public void clearLegalSquares() {
        for (ChessSquare square : chessSquares.values()) {
            square.setLegal(false);
        }
    }

    public void clearCapturableSquares() {
        for (ChessSquare square : chessSquares.values()) {
            square.clearCapturable();
            ChessPiece piece = square.getContents();
            if (piece != null) {
                piece.setCapturable(false);
            }
        }
    }

    public void clearThreatenedSquares() {
        for (ChessSquare square : chessSquares.values()) {
            square.setThreatened(false);
        }
    }

    public void resetSquares() {
        for (ChessSquare square : chessSquares.values()) {
            square.reset();
        }
    }

    public void setCurrentSquare(String squareName, boolean choice) {
        try {
            getChessSquares().get(squareName).setCurrent(choice);
        } catch (Exception ex) {
        }
    }

    public boolean getCurrentSquare(String squareName) {
        try {
            return getChessSquares().get(squareName).getCurrent();
        } catch (Exception ex) {
        }
        return false;
    }

    public void setLegalMoveSquares(String squareName, boolean choice) {
        try {
            getChessSquares().get(squareName).setLegal(choice);
        } catch (Exception ex) {
        }
    }

    public boolean getSquareHighlight(String squareName) {
        try {
            return getChessSquares().get(squareName).getLegal();
        } catch (Exception ex) {
        }
        return false;
    }

    public void addToCapturable(String squareName, ChessPiece piece) {
        try {
            getChessSquares().get(squareName).addCapturable(piece);
        } catch (Exception ex) {
        }
    }

    public boolean getSquareCapturable(String squareName) {
        try {
            return !getChessSquares().get(squareName).getCapturable().isEmpty();
        } catch (Exception ex) {
        }
        return false;
    }

    public void setSquareThreatened(String squareName, boolean choice) {
        try {
            getChessSquares().get(squareName).setThreatened(choice);
        } catch (Exception ex) {
        }
    }

    public boolean getSquareThreatened(String squareName) {
        try {
            return getChessSquares().get(squareName).getThreatened();
        } catch (Exception ex) {
        }
        return false;
    }

    public void setSquareLastMove(String squareName, boolean choice) {
        try {
            getChessSquares().get(squareName).setLastMove(choice);
        } catch (Exception ex) {
        }
    }

    public boolean getSquareLastMove(String squareName) {
        try {
            return getChessSquares().get(squareName).getLastMove();
        } catch (Exception ex) {
        }
        return false;
    }

    public void captureChessPiece(ChessPiece piece) {
        capturedPieces.add(piece);
        gui.capturedPiecesDialog.addCapture(piece);
        chessPieces.remove(piece);
        piece.getPosition().clearContents();
    }

    /**
     * @return the whiteKing
     */
    public King getWhiteKing() {
        return whiteKing;
    }

    /**
     * @return the blackKing
     */
    public King getBlackKing() {
        return blackKing;
    }

    /**
     * @return the whiteKingSideRook
     */
    public Rook getWhiteKingSideRook() {
        return whiteKingSideRook;
    }

    /**
     * @return the whiteQueenSideRook
     */
    public Rook getWhiteQueenSideRook() {
        return whiteQueenSideRook;
    }

    /**
     * @return the blackKingSideRook
     */
    public Rook getBlackKingSideRook() {
        return blackKingSideRook;
    }

    /**
     * @return the blackQueenSideRook
     */
    public Rook getBlackQueenSideRook() {
        return blackQueenSideRook;
    }

    /**
     * @return the isCheck
     */
    public String getIsCheck() {
        return isCheck;
    }

    /**
     * Update the chessSquares
     */
    public class ChessSquareUpdateThread implements Runnable {

        public void run() {
            while (true) {

                // if the screen is not disabled, cycle through chessSquares
                if (!gui.glassPane.isScreenDisabled()) {
                    for (ChessSquare square : chessSquares.values()) {
                        square.testMouseOver();
                    }
                }

                try {
                    Thread.sleep(20);
                } catch (Exception ex) { }
            }
        }
    }
}
